#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/md5.h>
using namespace std;

#define MULTICAST_GROUP "224.1.1.1"
#define MULTICAST_PORT 5007
#define BUFFER_SIZE 4096

//计算文件的MD5哈希
string calculateFileHash(const string& filePath)
{
    ifstream file(filePath, ios::binary);
    if (!file) throw runtime_error("Failed to open file: " + filePath);

    MD5_CTX md5Context;
    MD5_Init(&md5Context);

    char buffer[BUFFER_SIZE];
    while (file.read(buffer, sizeof(buffer)))
	{
        MD5_Update(&md5Context, buffer, file.gcount());
    }
    MD5_Update(&md5Context, buffer, file.gcount());

    unsigned char hash[MD5_DIGEST_LENGTH];
    MD5_Final(hash, &md5Context);

    string hashStr;
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++)
	{
        char hex[3];
        sprintf(hex, "%02x", hash[i]);
        hashStr += hex;
    }
    return hashStr;
}

void sendFile(const string& filePath)
{
    //创建UDPSocket
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
	{
        perror("socket");
        return;
    }

    //设置多播TTL
    unsigned char ttl = 2;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
	{
        perror("setsockopt(IP_MULTICAST_TTL)");
        close(sock);
        return;
    }

    //获取文件信息
    string fileName = filePath.substr(filePath.find_last_of("/\\") + 1);
    ifstream file(filePath, ios::binary | ios::ate);
    if (!file)
	{
        cerr << "Failed to open file: " << filePath << endl;
        close(sock);
        return;
    }
    size_t fileSize = file.tellg();
    file.close();
    string fileHash = calculateFileHash(filePath);

    //配置多播地址
    sockaddr_in multicastAddr{};
    multicastAddr.sin_family = AF_INET;
    multicastAddr.sin_addr.s_addr = inet_addr(MULTICAST_GROUP);
    multicastAddr.sin_port = htons(MULTICAST_PORT);

    //发送文件元数据（文件名|大小|哈希）
    string metadata = fileName + "|" + std::to_string(fileSize) + "|" + fileHash;
    if (sendto(sock, metadata.c_str(), metadata.size(), 0, (sockaddr*)&multicastAddr, sizeof(multicastAddr)) < 0)
	{
        perror("sendto(metadata)");
        close(sock);
        return;
    }
    cout << "[Sender] Sent metadata: " << metadata << endl;

    //分块发送文件内容
    ifstream inFile(filePath, ios::binary);
    if (!inFile)
	{
        cerr << "Failed to reopen file for reading." << endl;
        close(sock);
        return;
    }

    char buffer[BUFFER_SIZE];
    size_t bytesSent = 0;
    while (!inFile.eof())
	{
        inFile.read(buffer, sizeof(buffer));
        streamsize bytesRead = inFile.gcount();
        if (bytesRead > 0)
		{
            if (sendto(sock, buffer, bytesRead, 0, (sockaddr*)&multicastAddr, sizeof(multicastAddr)) < 0)
			{
                perror("sendto(data)");
                break;
            }
            bytesSent += bytesRead;
            cout << "\r[Sender] Progress: " << bytesSent << "/" << fileSize << " bytes" << flush;
        }
    }
    cout << "\n[Sender] File sent successfully!" << endl;
    inFile.close();
    close(sock);
}

int main()
{
    string filePath;
    cout << "Enter file path to send: ";
    getline(cin, filePath);

    sendFile(filePath);
    return 0;
}